In [ ]:
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
The Fourier transform is one of the most important discoveries in applied math, with particular importance for the study of partial differential equations. Given that the majority of our models of the physical world are based on PDEs, this makes it important for studying the universe. Of course, it's had a few practical applications as well.
This is a very good introduction to the concept.
For our purposes, though, let's just focus on the discrete, forward FT, aka the DFT
$$F_k = \sum_{j=0}^{N-1} f_j e^{-i \omega_k t_j}.$$This takes a series of data $f_j$ and produces another series of data $F_k$. The data must be sampled at equidistant points, that is $\Delta t$ must be constant. The DFT assumes the data $[f_0, f_1, \dots, f_{N-1}]$ is periodic. NB: I am here using the DFT assuming the data is a time signal and we are producing a frequency domain; this can be thought of as sampling a spatial direction $x$ with wavenumbers $k$ The frequencies are given by
$$\omega_k = \frac{2\pi k}{T}$$,
where is the total duration of the signal, and $-N/2 < k < N/2$. For simplicity, we'll assume only even sized inputs.
Problem 1a
Just looking at the definition of the DFT, attempt to determine the complexity of the algorithm. Write it down somewhere; discuss it with your friends.
Problem 1b For computation, it helps to notice that $F_k$ represents the $k$th element of an output array $F$ and similarly, $f_j$ is the $j$th element of the input array $f$. Thus, we can write the DFT as a matrix multiplication problem:
$$\mathbf{F} = \mathbf{\mathcal{A}} \mathbf{f},$$where $\mathbf{\mathcal{A}}$ is a square $N \times N$ matrix. The complexity of the algorithm should now be quite obvious. Was your guess correct?
Now, find $\mathbf{\mathcal{A}}$ by writing $\mathcal{A}_{jk}$ (note we avoid $i$ as an index to avoid confusion with $i = \sqrt{-1}$).
There is a VERY interesting property you should find when you calculate $\mathbf{\mathcal{A}}_{ij}$. It is related to the fact that the call signature for the simple_ft below only includes the data itself, and not the times the data is sampled at.
Problem 1c
Now, we need to make sure we use numpy's array features to ensure your DFT function is not unusably slow (remember, complexity only deals with the asymptotic behavior of a function!).
Once you have $\mathbf{\mathcal{A}}_{ij}$, fill in the function definition for simple_ft.
Questions to think about while writing this function:
A be? Hints are hidden in this cell. Reveal them if you need them!
In [ ]:
def simple_ft(f):
"""given an (in general) complex array f, return the discrete fourier transform, F.
"""
pass
Once you have this, test it against this data. The correct solution is given in test_ft_data. Make a plot showing that your solution matches the test data.
In [ ]:
t = np.linspace(0, 2*np.pi,10,endpoint=False)
f = np.sin(t)
In [ ]:
test_ft_data = np.array([ 1.22464680e-16+0.00000000e+00j, -5.50355954e-16-5.00000000e+00j,
1.22464680e-16-2.22044605e-16j, 1.94404292e-16+2.86638918e-16j,
1.22464680e-16-0.00000000e+00j, 9.95799250e-17+0.00000000e+00j,
1.22464680e-16+0.00000000e+00j, 1.94404292e-16-2.86638918e-16j,
1.22464680e-16+2.22044605e-16j, -5.50355954e-16+5.00000000e+00j])
In [ ]:
f_c = simple_ft(f)
You have likely heard of the "FFT", or "fast fourier transform." This is an algorithm for calculating the discrete fourier transform; you have already coded a different one. We are going to see
Now, with your simple_ft function in hand, run it on data sets increasing in size over at least two orders of magnitude with %timeit -o.
Do the same thing but use np.fft.fft on the exact same data. Plot both run times (appropriately normalized, as we did in the lecture) on the same log-log plot.
Do you see anything interesting?
In [ ]:
# no code snippets provided here